"""
Actory: a Python-based domain-specific language (DSL) for 
specifying finite state machines (FSM).

Copyright 2012, Tim Menzies, tim@menzies.us,
Creative Commons Attribution 3.0 Unported License.

Share and enjoy. 
:-)

Todo
====
?
"""

from pprint import pprint
import random,sys

def noop(*lst) :
	"Input any number of arguments and nothing."
	1

class World(object):
	"A World is the working memory passed around the inference."
	def __init__(self, printflag=True):
                global PRINT_FLAG
                PRINT_FLAG = printflag
		self.steps=0
		self.visited={}

class Act(object):
	"""
  An Act connects States 'here' to 'there'. 

  All the Acts for one state are explored in an order biased by 
  'priority' (and higher priority acts get explored first). 

  Acts have 'gaurds' that check for relevance (default=True).

  Acts have 'actions' that fire if the guard is satisfied (default=noop).
  If an Act fires, then a text 'tag' is printed.
  """
	def __init__(self,here,there,tag,priority=1,guard=True,fx=noop) :
		"""Constructor. When creating a new Act, it is always installed
    into the outs list of the 'here' state."""
		self.there = there
		self.here  = here
		self.tag   = tag
		self.guard = guard
		self.fx    = fx
		self.priority = priority
		self.here.out(self)

	def sideEffect(self):
		"""Fire the side-effect associated with performing this Act."""
		return val(self.fx)

	def ready(self) :
		"""Check if this Act is ready to run."""
		return val(self.guard)

	def __repr__(self):
                if PRINT_FLAG:
        		"""Pretty print an Act"""
        		return (str(self.here) + ' to ' + 
						str(self.there) + ' [' + 
						str(self.tag) + '] * ' +
						str(self.priority))
		
class State(object):
	"A State is a pause point inside a Machine."
	def __init__(self,name) :
		"""
    States have a text 'name' and list of 'outs' Acts. For effeciency
    reasons, this list is sorted decreasing on Act priority order.
    The sum of all Act priorities in 'outs' is 'totalProbs'.
		"""		
		self.name = name
		self.outs = []
		self.totalProb = 0
		self.flags(name)

	def flags(self,name) :
		"""
    States have flags 'isEndState', 'isErrorState', 'isProgressState'.
    These are set from special symbols in the state name.
		"""
		self.isEndState      = '.' in name
		self.isErrorState    = '_' in name
		self.isProgressCycle = '#' in name

	def out(self,act) :
		"When we add an act to 'outs', we update 'totalProb' and re-sort."
		self.outs.append(act)
		self.totalProb += act.priority
		self.outs.sort(key = lambda x: -1*x.priority)
		return act

	def todo(self,lives) :
		"""
		Search 'outs' for an Act that is ready to fire. This search is
    biased by Act priorities.
		"""
		while lives > 0 :
			for one in often(self.outs,self.totalProb,
											 lambda x:x,
											 lambda x:x.priority):
				if one.ready():
					return one
			lives -= 1
		return None

	def onEntry(self,act,w,n,f) :
		"On entry to a state, do something."
		w.steps += 1
		if PRINT_FLAG:
                        print n + " Entering >>",self.name ,\
                             ' ('+act.tag+')'
	def onExit(self,act,w,n) :
		"On exit from a state, do something."
		1

	def goOn(self,act,w) :
		"Returns false if we are at error or end state, or if we are looping."
		n = inc(w.visited,self.name)
		if self.isErrorState:
			print "Error State"
			return False
		if self.isEndState:
			if PRINT_FLAG: print "End State"
			return False
		if self.looping(n) :
			return False
		return True
	def goOn1(self,act,w) :
		"Returns false if we are at error or end state, or if we are looping."
		n = inc(w.visited,self.name)
		if self.isErrorState:
			print "Error State"
			return False
		if self.isEndState:
			if PRINT_FLAG: print "End State"
			return False
		return True

	def looping(self,n) :
		"We are looping if this is not a progress cycle and we've been here before."
		return  n > 1 and not self.isProgressCycle

	def __repr__(self) :
		"Pretty print this object."
		if PRINT_FLAG: return '<' + str(self.name) + '> : ' + str(len(self.outs))

class Machine(object):
	"A machine holds many Acts."
	def __init__(self,str="") :
		"""Constructor. Machines have a 'start' state 
    and a list of 'actions' and 'states'."""
		self.actions=[]
		self.states={}
		self.start= None
		self.create(str)
		self.priority = 5
		self.here = None
		self.name = ""

        def setPriority(self, p):
                self.priority = p
                
	def create(self) :
		"Build the actions within this machine (implemented by subclasses)"
		return self

	def state(self,name) :
		"""
    Ensure a state called 'name' exists. If this the first state
    in this machine, make it the start state.
		"""
		if self.states.has_key(name): 
			s = self.states[name]
		else:
			s = State(name)
			if not self.start: 
				self.start = s
			self.states[name] = s
		return s

	def act(self,before,after,tag,priority=1,guard=True,fx=True) :
		"Add an Act to this machine."
		t = Act(before,after,tag,priority,guard,fx)
		self.actions.append(t)
		return t
	
	def run(self,world=World(),lives=3) :
		"Find new Acts that are ready to go and fire them."
		global The
		The = world
		if not self.here:
                        self.here = self.start
		while True:
			act = self.here.todo(lives)
			if act:
				self.here.onExit(act,world,self.name)
				act.sideEffect()
				self.here = act.there
				self.here.onEntry(act,world,self.name, True)
				if not self.here.goOn(act,world):
					break 
		return world

	def run_one(self,world,lives=3) :
                "Find a new act and fire it."
                global The
		The = world
		if not self.here:
                        self.here = self.start
		while True:
			act = self.here.todo(lives)
			if act:
				self.here.onExit(act,world,self.name)
				act.sideEffect()
				self.here = act.there
				self.here.onEntry(act,world,self.name, self.here.goOn(act,world))
				break
		return world

class Easy(Machine) :
	"Easy is a quick way of creating machines from a string."
	def create(self,str) :
		"Parsing the strings. Dirty."
		lines=filter(None,lstrips(str.split('\n')))
		last=None
		states = []
		for line in [lstrips(x.split('|')) for x in lines]: 
			if len(line) != 5:
				print 'bad line ' + line
				continue
			line[4],weight = self.priority(line[4])
			comment = line[3]
			here    = self.state(line[0] or last)
			there   = self.state(line[4])
			guard   = True
			if line[1]:
				guard = line[1].replace('?','The.')
				guard = eval('lambda : ' + guard)
			sideEffect = True
			if line[2]:
				sideEffect = line[2].replace('?','The.')
				sideEffect = eval('lambda : ' + sideEffect)
 			if line[0] : last = line[0]
			self.act(here,there,comment,
							 priority=weight, guard=guard,
							 fx=sideEffect)
	def priority(self,str) :
		"If a line ends in * N, then the priority of that Act is N."
		if not '*' in str: return str,1.0
		tmp= str.split('*')
		return tmp[0],float(tmp[1])

#------------------------------------------------------------
# utilties

def lstrips(lst) :
	return [x.strip() for x in lst]

def val(x) :
	"""Return a value; if passed a lambda, then run it."""
	def functionp(x) : return type(x).__name__ == 'function' 
	return x() if functionp(x) else x

def inc(d,key,delta=1):
	"""Increment a counter at key[d]."""
	n = d[key] = d.get(key,0) + delta
	return n

def egs(*lst):
	"""Simple regression tool: runs all the functions in 'lst'."""
	for i in lst: 
		if PRINT_FLAG: print '\n----|',i.func_name,'|','-'*40,'\n'
		i()

def often(seq,total,item,weight) :
	"""
  Iterator. Selects with some bias from a sequence of items x.
  'Item' and 'weight' are lambda bodies.
  Each item(x) contributes weight(x) to a 'total' probability.
  The sum of all contributions is 'total'.
	"""
	while True:
		chosen = random.uniform(0, total)
		count  = 0
		for x in seq:
			count += weight(x)
			if count > chosen:
				yield item(x)
				break
